package fid

import (
	"fmt"
	"math"
	"reflect"
	"strconv"
	"strings"
	"time"

	"gitee.com/gaoguangting/tools/timeutil"
	"github.com/sirupsen/logrus"
)

func Copy(data map[string]interface{}, typ reflect.Type, val reflect.Value) {
	if typ.Kind() == reflect.Ptr {
		if val.IsNil() {
			val.Set(reflect.New(typ.Elem()))
		}
		Copy(data, typ.Elem(), val.Elem())
		return
	}
	switch typ.Kind() {
	case reflect.Struct: //Map -> Struct
		var iterRun func(it reflect.Type, iv reflect.Value)
		iterRun = func(it reflect.Type, iv reflect.Value) {
			for i := 0; i < it.NumField(); i++ {
				itypField := it.Field(i)
				ival := iv.Field(i)
				if itypField.Anonymous {
					iterRun(itypField.Type, ival)
					continue
				}
				key := itypField.Name
				jsonTag := itypField.Tag.Get("json")
				if idx := strings.Index(jsonTag, ","); idx > 0 {
					jsonTag = string(jsonTag[0:idx])
				}
				if len(jsonTag) > 0 && jsonTag != "-" {
					key = jsonTag
				} else {
					key = nameToTarin(key)
				}
				if v, ok := data[key]; ok {
					itemCopy(v, itypField.Type, ival)
				} else {
					itemDefault(itypField, ival)
				}
			}
		}
		iterRun(typ, val)
	case reflect.Map: // -> Map
		itemCopy(data, typ, val)
	default:
		logrus.WithFields(logrus.Fields{
			"data": data,
			"type": reflect.TypeOf(data).Name(),
		}).Errorf("convert map to %v error", typ.Name())
		return
	}
}

func isToolsTime(typ reflect.Type) bool {
	toolsTimeTyp := reflect.TypeOf(timeutil.NumTime(0))
	return typ.Name() == toolsTimeTyp.Name() && typ.PkgPath() == toolsTimeTyp.PkgPath()
}

func itemDefault(field reflect.StructField, val reflect.Value) {
	if !val.CanSet() {
		return
	}
	switch field.Type.Kind() {
	case reflect.Int64:
		if isToolsTime(field.Type) {
			tags := strings.Split(field.Tag.Get("json"), ",")
			if len(tags) > 1 {
				switch tags[1] {
				case "min":
					val.SetInt(int64(timeutil.MinTime))
				case "max":
					val.SetInt(int64(timeutil.MaxTime))
				}
			}
		}
	}
}

func itemCopy(data interface{}, typ reflect.Type, val reflect.Value) {
	if !val.CanSet() {
		logrus.WithFields(logrus.Fields{
			"data":       data,
			"value_type": typ.Name(),
		}).Errorln("[ROUTE.COPY] copy value error")
		return
	}
	if data == nil {
		return
	}
	switch typ.Kind() {
	case reflect.Bool:
		switch data.(type) {
		case bool: // Bool -> Bool
			val.SetBool(data.(bool))
		case int, int8, int16, int32, int64: //int -> Bool
			val.SetBool(reflect.ValueOf(data).Int() != 0)
		case uint, uint8, uint16, uint32, uint64: //uint->Bool
			val.SetBool(reflect.ValueOf(data).Uint() != 0)
		case float32, float64: //float -> Bool
			val.SetBool(reflect.ValueOf(data).Float() != 0)
		case string: // String -> Bool
			val.SetBool(strings.ToLower(data.(string)) == "true")
		}
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		switch data.(type) {
		case int, int8, int16, int32, int64: //int -> int
			val.SetInt(reflect.ValueOf(data).Int())
		case uint, uint8, uint16, uint32, uint64: //uint->int
			val.SetInt(int64(reflect.ValueOf(data).Uint()))
		case float32, float64: //float -> int
			val.SetInt(int64(reflect.ValueOf(data).Float()))
		case string: // String -> int
			if isToolsTime(typ) {
				zone := time.FixedZone("Beijing", 8*3600)
				if t, err := time.ParseInLocation(timeutil.TIMEFormatter, data.(string), zone); err == nil {
					val.SetInt(t.UnixNano())
				}
			} else {
				i64, err := strconv.ParseInt(data.(string), 10, 0)
				if err != nil {
					logrus.WithFields(logrus.Fields{
						"data": data,
						"type": reflect.TypeOf(data).Name(),
						"err":  err,
					}).Error("convert string to int error")
					return
				}
				val.SetInt(i64)
			}
		}
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		switch data.(type) {
		case int, int8, int16, int32, int64: //int -> uint
			val.SetUint(uint64(math.Abs(float64(reflect.ValueOf(data).Int()))))
		case uint, uint8, uint16, uint32, uint64: //uint->uint
			val.SetUint(reflect.ValueOf(data).Uint())
		case float32, float64: //float -> uint
			val.SetUint(uint64(math.Abs(reflect.ValueOf(data).Float())))
		case string: // String -> uint
			ui64, err := strconv.ParseUint(data.(string), 10, 0)
			if err != nil {
				logrus.WithFields(logrus.Fields{
					"data": data,
					"type": reflect.TypeOf(data).Name(),
					"err":  err,
				}).Error("convert string to uint error")
				return
			}
			val.SetUint(ui64)
		}
	case reflect.Float32, reflect.Float64:
		switch data.(type) {
		case int, int8, int16, int32, int64: //int -> float
			val.SetFloat(float64(reflect.ValueOf(data).Int()))
		case uint, uint8, uint16, uint32, uint64: //uint->float
			val.SetFloat(float64(reflect.ValueOf(data).Uint()))
		case float32, float64: //float -> float
			val.SetFloat(reflect.ValueOf(data).Float())
		case string: // String -> float
			f64, err := strconv.ParseFloat(data.(string), 64)
			if err != nil {
				logrus.WithFields(logrus.Fields{
					"data": data,
					"type": reflect.TypeOf(data).Name(),
					"err":  err,
				}).Error("convert string to float error")
				return
			}
			val.SetFloat(f64)
		}
	case reflect.String:
		var str string
		switch data.(type) {
		case int, int8, int16, int32, int64: //int -> float
			str = fmt.Sprintf("%d", data)
		case float32, float64: //float -> float
			f := float64(reflect.ValueOf(data).Float())
			fi := int64(f)
			if f >= float64(fi) && f <= float64(fi)+0.000000000001 {
				str = fmt.Sprintf("%d", fi)
			} else {
				str = fmt.Sprintf("%f", f)
			}
		default:
			str = fmt.Sprintf("%v", data)
		}
		val.SetString(str)
	case reflect.Ptr:
		if val.IsNil() {
			val.Set(reflect.New(typ.Elem()))
		}
		itemCopy(data, typ.Elem(), val.Elem())
	case reflect.Struct:
		switch data.(type) {
		case map[string]interface{}:
			Copy(data.(map[string]interface{}), typ, val)
		default:
			logrus.WithFields(logrus.Fields{
				"data": data,
				"type": reflect.TypeOf(data).Name(),
			}).Error("convert data to struct")
			return
		}
	case reflect.Slice:
		if val.IsNil() {
			val.Set(reflect.MakeSlice(typ, 0, 0))
		}
		dataTyp := reflect.TypeOf(data)
		if dataTyp.Kind() == reflect.Slice || dataTyp.Kind() == reflect.Array {
			dataVal := reflect.ValueOf(data)
			if !dataVal.IsNil() && dataVal.Len() > 0 {
				for i := 0; i < dataVal.Len(); i++ {
					sliceVal := reflect.New(typ.Elem()).Elem()
					itemCopy(dataVal.Index(i).Interface(), typ.Elem(), sliceVal)
					val.Set(reflect.Append(val, sliceVal))
				}
			}
		} else {
			logrus.WithFields(logrus.Fields{
				"data": data,
				"type": reflect.TypeOf(data).Name(),
			}).Error("convert data to slice")
			return
		}
	case reflect.Map:
		if val.IsNil() {
			val.Set(reflect.MakeMap(typ))
		}
		dataTyp := reflect.TypeOf(data)
		if dataTyp.Kind() == reflect.Map {
			dataVal := reflect.ValueOf(data)
			for _, mk := range dataVal.MapKeys() {
				mapKeyValue := reflect.New(typ.Key()).Elem()
				itemCopy(mk.Interface(), typ.Key(), mapKeyValue)
				mapValueValue := reflect.New(typ.Elem()).Elem()
				itemCopy(dataVal.MapIndex(mk).Interface(), typ.Elem(), mapValueValue)
				val.SetMapIndex(mapKeyValue, mapValueValue)
			}
		} else {
			logrus.WithFields(logrus.Fields{
				"data": data,
				"type": reflect.TypeOf(data).Name(),
			}).Error("convert data to map")
			return
		}
	}
}
